/* **********************************************************************
 * Copyright (c) Ericsson 2007. All Rights Reserved.
 * Reproduction in whole or in part is prohibited without the 
 * written consent of the copyright owner. 
 * 
 * ERICSSON MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY 
 * OF THE SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 
 * TO THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR 
 * PURPOSE, OR NON-INFRINGEMENT. ERICSSON SHALL NOT BE LIABLE FOR ANY 
 * DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR 
 * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES. 
 * 
 * **********************************************************************/

package com.ericsson.sds.ijcu.client;

import java.io.IOException;

import javax.microedition.io.ConnectionNotFoundException;
import javax.microedition.lcdui.Alert;
import javax.microedition.lcdui.AlertType;
import javax.microedition.lcdui.Command;
import javax.microedition.lcdui.CommandListener;
import javax.microedition.lcdui.Display;
import javax.microedition.lcdui.Displayable;
import javax.microedition.lcdui.Form;
import javax.microedition.lcdui.StringItem;
import javax.microedition.lcdui.TextField;
import javax.microedition.midlet.MIDlet;
import javax.microedition.midlet.MIDletStateChangeException;

import javay.microedition.ims.Configuration;
import javay.microedition.ims.ConnectionState;
import javay.microedition.ims.ConnectionStateListener;
import javay.microedition.ims.ImsException;
import javay.microedition.ims.ServiceClosedException;
import javay.microedition.ims.core.CoreService;
import javay.microedition.ims.core.CoreServiceListener;
import javay.microedition.ims.core.Message;
import javay.microedition.ims.core.PageMessage;
import javay.microedition.ims.core.Reference;
import javay.microedition.ims.core.Session;
import javay.microedition.ims.core.SessionListener;
import javay.microedition.ims.core.media.FramedMedia;
import javay.microedition.ims.core.media.FramedMediaListener;
import javay.microedition.ims.core.media.Media;
import javay.microedition.io.Connector;

import com.ericsson.ijcu.IjcuUserManagement;

public class IjcuChat extends MIDlet
{
    /**
     * Area to display all messages and status
     */
    private StringItem chatArea;

    private TextField domainURIField;

    private TextField outboundProxyURIField;

    private TextField publicUserIdField;

    private TextField realmField;

    private TextField privateUserIdField;

    private TextField mobilePhoneNumberField;

    private TextField passwordField;

    /**
     * Current session
     */
    private Session session;

    /**
     * Outgoing Frame media
     */
    private FramedMedia framedMedia;

    /**
     * Service Id
     */
    private String scheme = "imscore://";

    private String iari = "com.ericsson.client.IjcuChat";

    private String serviceId = "chat";

    private String aliceUserId = "sip:alice@ericsson.com";

    private String IARI_PREFIX = "urn%3Aurn-xxx%3A";

    private String transport = "tcp";

    private static String contentType = "text/plain";

    /**
     * The core service for creating service methods to be sent to remote user.
     */
    private CoreService coreService;

    /**
     * Initialize the resources Display the main form
     */
    public void startApp() throws MIDletStateChangeException
    {
        showRegistrationForm();
        System.out.println("App started");
    }

    private void showRegistrationForm()
    {
        Form registerForm = new Form("Register");

        Command registerCommand = new Command("Register", Command.OK, 1);
        Command exitCommand = new Command("Exit", Command.EXIT, 1);

        registerForm.addCommand(registerCommand);
        registerForm.addCommand(exitCommand);
        getDisplay().setCurrent(registerForm);

        domainURIField = new TextField("DomainURI:", "sip:ericsson.com", 40, TextField.ANY);
        outboundProxyURIField = new TextField("OutboundProxyURI:", "sip:127.0.0.1:5081", 40, TextField.ANY);
        publicUserIdField = new TextField("PublicUserId:", aliceUserId, 40, TextField.ANY);
        realmField = new TextField("realm:", "ericsson.com", 40, TextField.ANY);
        privateUserIdField = new TextField("PrivateUserId:", "alice@ericsson.com", 40, TextField.ANY);
        mobilePhoneNumberField = new TextField("MobilePhoneNumber:", "5143457900", 40, TextField.DECIMAL);
        passwordField = new TextField("Password", "alice", 10, TextField.PASSWORD);
        passwordField.setString("alice");

        registerForm.append(domainURIField);
        registerForm.append(outboundProxyURIField);
        registerForm.append(publicUserIdField);
        registerForm.append(realmField);
        registerForm.append(privateUserIdField);
        registerForm.append(mobilePhoneNumberField);
        registerForm.append(passwordField);

        registerForm.setTitle("Chat Client");

        registerForm.setCommandListener(new CommandListener()
        {
            public void commandAction(Command command, Displayable displayable)
            {
                if(command.getCommandType() == Command.OK)
                {
                    RegisterThread registerThread = new RegisterThread();
                    Thread thread = new Thread(registerThread);
                    thread.start();
                }
                else if(command.getCommandType() == Command.EXIT)
                {
                    exit();
                }
            }
        });
    }

    /**
     * Pause the MIDlet
     */
    public void pauseApp()
    {
    }

    /**
     * Destroy the MIDLet
     * 
     * @param unconditional: <code>true</code>: if it's mandatory to destroy the MIDlet <code>false</code>: if it's not mandatory to destroy the
     *            MIDlet
     */
    public void destroyApp(boolean unconditional)
    {
    }

    /**
     * Check if the input URI is valid
     * 
     * @param uri: input URI
     * @return <code>true</code> the input is valid, <code>false</code> otherwise
     */
    private boolean isUriValid(String uri)
    {
        if(uri.compareTo("") == 0 || !uri.startsWith("sip:"))
        {
            return false;
        }
        return true;
    }

    /**
     * Thread to unregister the phone and release resources before the MIDlet is destroyed.
     */
    protected class CleanUpThread extends Thread
    {
        /**
         * Un-register the phone, release resources and destroy the MIDlet
         */
        public void run()
        {
            try
            {
            	if (coreService != null)
            	{
            		coreService.close();
            	}
            }
            catch (IOException e)
            {
                e.printStackTrace();
            }
        }
    }

    /**
     * Thread to register the phone.
     */
    protected class RegisterThread implements Runnable
    {

        public void run()
        {
            register();
        }

        public void register()
        {
            try
            {
            	IjcuUserManagement ijcuUserManagement =  IjcuUserManagement.getInstance();            	            
            	ijcuUserManagement.setDomainURI(domainURIField.getString());            	
            	ijcuUserManagement.setOutboundProxyURI(outboundProxyURIField.getString());
            	ijcuUserManagement.setPublicUserId(publicUserIdField.getString());            	
            	ijcuUserManagement.setRealm(realmField.getString());
            	ijcuUserManagement.setPrivateUserId(privateUserIdField.getString());
            	ijcuUserManagement.setPassword(passwordField.getString());
            	ijcuUserManagement.setTransport(transport);            	            	
            	ijcuUserManagement.setPlatform(IjcuChat.this);

                Configuration myConfiguration = Configuration.getConfiguration();
                String[][] properties = new String[][] {{ "Framed", contentType },
                                                        { "CoreService", serviceId, IARI_PREFIX + iari, "", "" } };
                myConfiguration.setRegistry(iari, IjcuChat.class.getName(), properties);

                ConnectionState connectionState = ConnectionState.getConnectionState();
                connectionState.setListener(new ConnectionStateAdapter());

                String url = scheme + iari + ";serviceId=" + serviceId;

                System.out.println("URL: "+url);
                
                coreService = (CoreService) Connector.open(url);
                CoreServiceClientAdapter coreServiceAdapter = new CoreServiceClientAdapter();
                coreService.setListener(coreServiceAdapter);

                System.out.println("Calling initializeSession()");
                initializeSession();
            }
            catch (IllegalArgumentException e3)
            {
                alert("Can not register the  phone: receive IllegalArgumentException");
                e3.printStackTrace();
            }
            catch (ConnectionNotFoundException e2)
            {
                alert("Can not register the  phone: receive ConnectionNotFoundException");
            }
            catch (IOException e1)
            {
                alert("Can not register the  phone: receive IOException");
            }
            catch (Exception e)
            {
                alert("Can not register the  phone: receive Exception ");
            }

        }
    }

    /**
     * Thread to send an CHAT invitation to the remote user.
     */
    private class StartSessionThread implements Runnable
    {
        private String remoteUserId;

        public StartSessionThread(String remoteUserId)
        {
            this.remoteUserId = remoteUserId;
        }

        public void run()
        {
            invite(remoteUserId);
        }

        /**
         * Validate the input of remote URI and send an INVITE SIP message to the remote URI
         */
        private void invite(String remoteUserId)
        {
            if(!isUriValid(remoteUserId))
            {
                displayAlert("Please enter a valid URI", true);
                return;
            }

            try
            {
                session = coreService.createSession(publicUserIdField.getString(), remoteUserId);
                SessionAdapter sessionAdapter = new SessionAdapter();
                session.setListener(sessionAdapter);

                framedMedia = (FramedMedia) session.createMedia("FramedMedia", Media.DIRECTION_SEND_RECEIVE);
                framedMedia.setAcceptedContentTypes(new String[] { "text/plain" });

                FramedMediaAdapter outgoingFramedAdapter = new FramedMediaAdapter();
                framedMedia.setListener(outgoingFramedAdapter);

                showPendingSession();

                session.start();
            }
            catch (IllegalStateException e)
            {
                displayAlert("Problem starting a session-IllegalStateException", true);
                e.printStackTrace();
            }
            catch (ImsException e1)
            {
                displayAlert("Problem starting a session-ImsException", true);
                e1.printStackTrace();
            }
            catch (ServiceClosedException e2)
            {
                displayAlert("Problem starting a session-ServiceClosedException", true);
                e2.printStackTrace();
            }
        }

        private void showPendingSession()
        {
            displayAlert("Sending invitation", false);

            Form sessionPendingForm = new Form("Session Pending");
            sessionPendingForm.setTitle("Invitation has been sent, waiting for reply");

            Command exitCommand = new Command("Exit", Command.EXIT, 1);
            sessionPendingForm.addCommand(exitCommand);

            sessionPendingForm.setCommandListener(new CommandListener()
            {
                public void commandAction(Command command, Displayable displayable)
                {
                    if(command.getCommandType() == Command.EXIT)
                    {
                        exit();
                    }
                }
            });
            getDisplay().setCurrent(sessionPendingForm);
        }
    }

    /**
     * Thread to end a CHAT session with the remote user.
     */
    private class EndSessionThread implements Runnable
    {
        public void run()
        {
            bye();
        }

        /**
         * Clear the output area and send a BYE SIP message.
         */
        private void bye()
        {
            chatArea.setText("");
            session.terminate();
        }
    }

    /**
     * Thread to send media to the remote user.
     */
    private class SendThread implements Runnable
    {
        private String message;

        public SendThread(String message)
        {
            this.message = message;
        }

        public void run()
        {
            send(message);
        }

        /**
         * Send Media frames
         */
        private void send(String message)
        {
            try
            {
                framedMedia.sendBytes(message.getBytes(), "text/plain", null);
            }
            catch (Exception e)
            {
                e.printStackTrace();
                displayAlert("Problem sending message!", true);
            }
        }
    }

    /**
     * Thread to accept a CHAT invitation
     */
    private class AcceptThread implements Runnable
    {
        public void run()
        {
            accept();
        }

        /**
         * Accept the CHAT invitation from remote user. Refresh the main form.
         */
        private void accept()
        {
            try
            {
                SessionAdapter terminalSessionAdapter = new SessionAdapter();
                session.setListener(terminalSessionAdapter);
                session.accept();

                FramedMediaAdapter incomingFramedAdapter = new FramedMediaAdapter();
                framedMedia = (FramedMedia) session.getMedia()[0];
                framedMedia.setListener(incomingFramedAdapter);

                startSession();
            }
            catch (IllegalStateException e)
            {
                e.printStackTrace();
                displayAlert("Problem accepting the session", true);
            }
        }

    }

    /**
     * Thread to reject a CHAT invitation
     */
    private class RejectThread implements Runnable
    {
        public void run()
        {
            reject();
        }

        /**
         * Reject the CHAT invitation from remote user. Refresh the main form.
         */
        private void reject()
        {
            try
            {
                session.reject();                
            }
            catch (IllegalStateException e)
            {
                e.printStackTrace();
                displayAlert("Problem rejecting the session", true);
            }
            initializeSession();
        }
    }

    /**
     * Exits the application
     */
    private void exit()
    {
        new CleanUpThread().start();
        try
        {
            Thread.sleep(1000);

            destroyApp(false);
            notifyDestroyed();
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }
    }

    private void initializeSession()
    {
        Form form = new Form("Initialize Session");
        form.setTitle("Send invitation");

        final TextField remoteUserId = new TextField("RemoteUserId", "sip:alice@ericsson.com", 40, TextField.ANY);
        remoteUserId.setLabel("Please enter the remote user Id:");
        form.append(remoteUserId);

        Command inviteCommand = new Command("Invite", Command.OK, 1);
        Command exitCommand = new Command("Exit", Command.EXIT, 1);
        form.addCommand(inviteCommand);
        form.addCommand(exitCommand);

        form.setCommandListener(new CommandListener()
        {
            public void commandAction(Command command, Displayable displayable)
            {
                if(command.getCommandType() == Command.OK)
                {
                    StartSessionThread startSessionThread = new StartSessionThread(remoteUserId.getString());
                    Thread thread = new Thread(startSessionThread);
                    thread.start();
                }
                else if(command.getCommandType() == Command.EXIT)
                {
                    exit();
                }
            }
        });

        getDisplay().setCurrent(form);
    }

    private void receiveInvitation()
    {
        Form form = new Form("Would you like to accept the invitation?");

        Command rejectCommand = new Command("Reject", Command.EXIT, 1);
        Command acceptCommand = new Command("Accept", Command.OK, 1);
        form.addCommand(rejectCommand);
        form.addCommand(acceptCommand);

        form.setCommandListener(new CommandListener()
        {

            public void commandAction(Command command, Displayable displayable)
            {
                if(command.getCommandType() == Command.OK)
                {
                    AcceptThread acceptThread = new AcceptThread();
                    Thread thread = new Thread(acceptThread);
                    thread.start();

                }
                else if(command.getCommandType() == Command.EXIT)
                {
                    RejectThread rejectThread = new RejectThread();
                    Thread thread = new Thread(rejectThread);
                    thread.start();
                }
            }

        });
        getDisplay().setCurrent(form);
    }

    private void terminateSession()
    {
        initializeSession();
    }

    private void startSession()
    {
        Form form = new Form("Chatting");
        final TextField message = new TextField("Enter a message to send:", "", 40, TextField.ANY);

        chatArea = new StringItem("\r\nMessages and status:\r\n", "");
        chatArea.setText("");
        form.append(message);
        form.append(chatArea);

        Command byeCommand = new Command("End", Command.EXIT, 1);
        Command sendCommand = new Command("Send", Command.OK, 1);
        form.addCommand(sendCommand);
        form.addCommand(byeCommand);

        form.setCommandListener(new CommandListener()
        {
            public void commandAction(Command command, Displayable displayable)
            {
                if(command.getCommandType() == Command.OK)
                {
                    SendThread sendThread = new SendThread(message.getString());
                    Thread thread = new Thread(sendThread);
                    thread.start();

                }
                else if(command.getCommandType() == Command.EXIT)
                {
                    EndSessionThread endSessionThread = new EndSessionThread();
                    Thread thread = new Thread(endSessionThread);
                    thread.start();
                }
            }
        });

        getDisplay().setCurrent(form);
    }

    private Display getDisplay()
    {
        return Display.getDisplay(this);
    }

    /**
     * Display an alert
     * 
     * @param alert: alert control
     * @param alertMessage: alert message
     */
    private void displayAlert(String alertMessage, boolean isError)
    {
        String title = isError ? "Error" : "Information";
        Alert alert = new Alert(title);
        alert.setString(alertMessage);
        getDisplay().setCurrent(alert);
        try
        {
            Thread.sleep(50);
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }
    }

    /**
     * Receive notifications about changes to the IMS connection.
     */
    class ConnectionStateAdapter implements ConnectionStateListener
    {
        public void imsConnected()
        {

        }

        public void imsDisconnected()
        {

        }
    }

    /**
     * Based on the event received from CoreServiceListener, handle events accordingly.
     */
    private class CoreServiceClientAdapter implements CoreServiceListener
    {
        public CoreServiceClientAdapter()
        {
            super();
        }

        public void pageMessageReceived(CoreService service, PageMessage message)
        {

        }

        public void referenceReceived(CoreService service, Reference reference)
        {

        }

        public void serviceClosed(CoreService service)
        {

        }

        public void sessionInvitationReceived(CoreService service, Session session)
        {
            IjcuChat.this.session = session;
            displayAlert("Receive incoming invitation from: " + session.getRemoteUserId(), false);

            receiveInvitation();

        }

        public void unsolicitedNotifyReceived(CoreService service, Message notify)
        {

        }
    };

    /**
     * Based on the event received from SessionListener, handle events accordingly.
     */
    private class SessionAdapter implements SessionListener
    {

        public void sessionAlerting(Session arg0)
        {
        }

        public void sessionReferenceReceived(Session arg0, Reference arg1)
        {
        }

        /**
         * Notification of the session status. Reset the main form based on the notification received.
         */
        public void sessionStartFailed(Session arg0)
        {
            displayAlert("Session can't be started", true);
            terminateSession();
        }

        /**
         * Notification of the session status. Reset the main form based on the notification received.
         */
        public void sessionStarted(Session arg0)
        {
            startSession();
        }

        /**
         * Notification of the ended session. Reset the main form.
         */
        public void sessionTerminated(Session arg0)
        {
            initializeSession();
        }

        public void sessionUpdateFailed(Session arg0)
        {
        }

        public void sessionUpdateReceived(Session arg0)
        {
        }

        public void sessionUpdated(Session arg0)
        {
        }
    };

    /**
     * Based on the event received from FramedMediaListener, handle events accordingly.
     */
    private class FramedMediaAdapter implements FramedMediaListener
    {

        public void connectionError(FramedMedia arg0)
        {
        }

        public void contentReceiveFailed(FramedMedia arg0, String arg1)
        {
        }

        /**
         * Display the received media on the output area.
         */
        public void contentReceived(FramedMedia arg0, String arg1, int arg2, String arg3)
        {
            String contentType = arg0.getContentType(arg1);
            try
            {
                if(contentType.equals("text/plain"))
                {
                    byte[] content = arg0.receiveBytes(arg1);
                    String message = new String(content);
                    appendText(message);
                }
            }
            catch (IllegalStateException e)
            {
                e.printStackTrace();
            }
            catch (IOException e1)
            {
                e1.printStackTrace();
            }

        }

        /**
         * Display the status of the delivery on the output area.
         */
        public void deliveryFailure(FramedMedia arg0, String arg1, int arg2, String arg3)
        {
            appendText("Failed to deliver the message");
        }

        /**
         * Display the status of the delivery on the output area.
         */
        public void deliverySuccess(FramedMedia arg0, String arg1)
        {
            appendText("Message was delivered sucessfully");
        }

        public void transferProgress(FramedMedia arg0, String arg1, int arg2, int arg3)
        {
        }
    };

    private void appendText(String message)
    {
        chatArea.setText(chatArea.getText() + message + "\r\n");
    }

    private void alert(String message)
    {
        Alert alert = new Alert("Error", "", null, AlertType.ERROR);
        alert.setString(message);
        getDisplay().setCurrent(alert);

        try
        {
            Thread.sleep(1000);
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }
        exit();
    }
}
